import { App, Provide, Config, Init, Inject } from '@midwayjs/decorator'
import { CacheManager } from '@midwayjs/cache'
import { Application } from 'egg'
import { gql } from 'graphql-request'
import { fromKeyLike, JWK } from 'jose/jwk/from_key_like'

@Provide()
export class AuthService {

  protected jwks: {
    keys?: JWK[]
  }

  @App()
  app: Application

  @Inject()
  cache: CacheManager

  @Config('jwt')
  jwtConfig: any

  @Init()
  async init() {
    this.jwks = {
      keys: [
        await fromKeyLike(this.jwtConfig.raw['publicKey'])
      ]
    }
  }

  async sign(payload, isRefresh?) {
    payload.isRefresh = Boolean(isRefresh)
    payload.role = payload.role || 'police'

    const claims = {
      ['x-hasura-allowed-roles']: ['police', 'operator', 'auditor', 'system-user', 'admin'],
      ['x-hasura-default-role']: 'police'
    }

    if (payload.id) {
      claims['x-hasura-user-id'] = payload.id
    }

    if (payload.role) {
      claims['x-hasura-role'] = payload.role
    }

    if (payload.department_id) {
      claims['x-hasura-department-id'] = String(payload.department_id)
    }

    payload['https://hasura.io/jwt/claims'] = claims

    return await this.app.jwt.sign(payload, isRefresh
      ? this.jwtConfig.refreshExpiresIn
      : this.jwtConfig.expiresIn)
  }

  getJwks() {
    return this.jwks
  }

  async generateToken(payload, isRefresh?) {
    if (!isRefresh) {
      return await this.sign(payload)
    }

    const refresh_token = await this.sign(payload, true)

    if (payload.id) {
      await this.app.gqlRequest(gql`
        mutation ($token: ie_user_token_insert_input!) {
          insert_ie_user_token (
            objects: [$token]
            on_conflict: {
              # save on hasura console
              constraint: ie_user_token_user_id_key
              update_columns: [token]
            }
          ) {
            affected_rows
          }
        }
      `, {
        token: {
          user_id: payload.id,
          token: refresh_token,
          update_at: 'now()'
        }
      })
    }

    return refresh_token
  }

  async getRefreshToken(user_id: string) {
    const {
      ie_user_token
    } = await this.app.gqlRequest(gql`
      query ($user_id: String!) {
        ie_user_token (where: { user_id: { _eq: $user_id } }) {
          token
        }
      }
    `, { user_id })

    if (ie_user_token.length === 0) return

    const [{
      token
    }] = ie_user_token

    try {
      // verify expired
      const decoded = await this.app.jwt.verify(token)

      if (decoded) {
        return token
      }
    } catch (err) {
      this.app.logger.error(err)
    }
  }

  async removeRefreshToken(user_id: string) {
    await this.app.gqlRequest(gql`
      mutation ($user_id: String!) {
        delete_ie_user_token (where: { user_id: { _eq: $user_id } }) {
          affected_rows
        }
      }
    `, { user_id })
  }

  async refreshToken(refresh_token: string) {
    try {
      const decoded = await this.app.jwt.verify(refresh_token)

      if (decoded) {
        const tokenExpired = await this.cache.get(decoded['id'])

        if (tokenExpired) {
          await this.cache.del(decoded['id'])
        } else if (decoded['isRefresh']) {
          const token = await this.generateToken(decoded)
          const updatedRefreshToken = await this.generateToken(decoded, true)
    
          return {
            token,
            refresh_token: updatedRefreshToken
          }
        }
      }
    } catch (err) {}
  }
}
